/*
 * PersistentCacheME.java
 *
 * Created on 05. August 2008, 16:25
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.nViron.nvGraffiti;

import instar.dg.Client.BaseClient;
import instar.dg.Common.Error;
import instar.dg.Common.ErrorListener;
import instar.dg.Common.ID;
import instar.dg.Debug.Debug;
import instar.dg.GraffitiCache.PersistentCache;
import instar.dg.Graffito.GraffitoBody;
import instar.dg.Graffito.GraffitoHeader;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Hashtable;

public class nvPersistentCacheSE2 extends PersistentCache {
    
    public static boolean isSupported() {
        return System.getProperty("microedition.io.file.FileConnection.version") != null;
    }
    
    protected String cacheDirectory = "/";
    
    final static String indexFilename = "cache.idx";

    protected final Hashtable popupBodiesInRange = new Hashtable();
    
    private final nvIOCacheSE ioCacheSe = new nvIOCacheSE();
    
    private final Object sync = new Object();
    
    /** Creates a new instance of PersistentCacheME */
    public nvPersistentCacheSE2( BaseClient baseClient, String cacheDir, int maxHeaderSize, int maxBodySize ) throws Exception {
        super( baseClient, maxHeaderSize, maxBodySize );
        cacheDirectory = cacheDir;
        checkDir();
        loadIndex();
    }

    @Override
	protected void addBody( final GraffitoBody body) {
        GraffitoHeader gh = (GraffitoHeader)headerInRange.get( body.getId() );
        if ( ( gh != null ) && gh.isPopUp() ) {
            Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addBody('" + body.getId().toString() + "'): graffito is a popup -> graffito is stored in memory too" );
            synchronized (popupBodiesInRange) {
                popupBodiesInRange.remove( body.getId() );
                popupBodiesInRange.put( body.getId(), body );
            }
        }
        ioCacheSe.saveAsync(cacheDirectory + "B" + body.getId().toString() + ".pgc", body, new ErrorListener() {

			public void onError(Error _error) {
                Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addBody(): " + _error.getErrorMessage() );
            }
        });
    }

    @Override
	protected void addHeader( final GraffitoHeader header) {
        ioCacheSe.saveAsync(cacheDirectory + "H" + header.getId().toString() + ".pgc", header, new ErrorListener() {

   			public void onError(Error _error) {
                //#debug
                 Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addHeader(): " + _error.getErrorMessage() );
            }
        });
    }

    protected void checkDir() throws Exception {
        ioCacheSe.checkDirectory(cacheDirectory);
    }

    @Override
    public void clear() {
        super.clear();
        popupBodiesInRange.clear();
        
        ioCacheSe.queueTask( new Runnable() {
  
			public void run() {
                try {
                    File fconn = new File(cacheDirectory );
                    // If no exception is thrown, then the URI is valid, but the file may or may not exist.
                    if ( fconn.exists() && fconn.isDirectory() ) {
                        String files[] = fconn.list();
                        for (String fn : files)
                        {
                          String filename = cacheDirectory + "/" + fn;
                          ioCacheSe.delete(filename);
                        }
//                        Debug.out.println( "delete " + cacheDirectory );
//                        fconn.delete();
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                    //#debug
                    Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] ERROR: PersistentCache.clear failed because of: " + ex.toString() );
                } catch (Throwable t) {
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.clear() throws " + t.toString());
                    t.printStackTrace();
                }
            }
        } );
    }

    @Override
	public boolean containsBody(ID id) {
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.containsBody('" + id.toString() + "')");

        boolean ret = popupBodiesInRange.containsKey(id);
        if (ret == false) {
            synchronized (bodyIndex) {
                ret = ioCacheSe.exists(cacheDirectory + "B" + id.toString() + ".pgc");
            }
        }
        return ret;
    }


    @Override
	public GraffitoBody loadBody(ID id) {
        Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadBody('" + id.toString() + "')" );
        GraffitoBody body = null;
        body = (GraffitoBody)popupBodiesInRange.get( id );
        if ( body == null ) {
            try {
                synchronized (bodyIndex) {
                    File fconn = new File( cacheDirectory + "B" + id.toString() + ".pgc" );
                    // If no exception is thrown, then the URI is valid, but the file may or may not exist.
                    if ( fconn.exists() && ( fconn.length() > 0 ) ) {
                        FileInputStream is = new FileInputStream(fconn);
                        DataInputStream dis = new DataInputStream(is);
                        body = new GraffitoBody();
                        // overwrite type id
                        dis.readInt();
                        body.readFromDataInput( dis );
                        dis.close();
                        is.close();
                    }
                    fconn = null;
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return body;
    }

    @Override
	protected GraffitoHeader loadHeader(ID id) {
        GraffitoHeader header = null;
        //#debug
        long begin = System.currentTimeMillis();
        String filename = cacheDirectory + "H" + id.toString() + ".pgc";
        synchronized (headerIndex) {
            try {
                header = (GraffitoHeader) ioCacheSe.load(filename, new GraffitoHeader());
            } catch (IOException cnfe ) {
                Debug.out.println( "loadHeader throws ConnectionNotFoundException -> delete id from index!" );
                headerIndex.remove( id );
            } catch (Throwable ex) {
                ex.printStackTrace();
            }
        }
        
        // if header has been loaded and is a popup which body hasn'nt already been loaded to to popupBodiesInRange table -> load it!
        if ( ( header != null ) && header.isPopUp() && containsBody( header.getId() ) && !popupBodiesInRange.containsKey( header.getId() ) ) {
            Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadHeader('" + id.toString() + "'): popup detected, which body is loaded but not in memory" );
            GraffitoBody gb = loadBody(header.getId() );
            if ( gb != null ) {
                popupBodiesInRange.put( header.getId(), gb );
            }
        }
        //#debug
        Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadHeader('" + filename + "') ends in " + (System.currentTimeMillis() - begin) + " ms." );
        return header;
    }


    /**
     * @deprecated Not used?
     */
    @Deprecated
	@Override
	protected void loadHeaderAsync(final ID id, final LoadListener callback) {
        ioCacheSe.queueTask( new Runnable() {

			public void run() {
                try {
                    GraffitoHeader gh = loadHeader( id );
                    if ( callback != null ) {
                        callback.graffitoHeaderLoaded( gh );
                    }
                } catch (Throwable t ) {
                    //#debug
                    Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadHeaderAsync() throws " + t.toString() );
                    t.printStackTrace();
                }
            }
        } );
    }

    private void loadIndex() {
        long begin = System.currentTimeMillis();
        Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadIndex()" );
        headerIndex.clear();
        bodyIndex.clear();
        File fconn = null;
        try {
            fconn = new File(cacheDirectory + indexFilename);
            // If no exception is thrown, then the URI is valid, but the file may or may not exist.
            if ( fconn.exists() ) {
                Debug.out.println( "... loading index" );

                FileInputStream is = new FileInputStream(fconn);
                DataInputStream dis = new DataInputStream(is);
                if ( fconn.length() > 0 ) {
                    int hcnt = dis.readInt();
                    for ( int i = 0; i < hcnt; i++ ) {
                        Header h = new Header();
                        h.readFromDataInput(dis);
                        headerIndex.add( h );
                    }

                    int bcnt = dis.readInt();
                    for ( int i = 0; i < bcnt; i++ ) {
                        Body b = new Body();
                        b.readFromDataInput( dis );
                        bodyIndex.add( b );
                    }
                }
                dis.close();
                is.close();
            } else {
                Debug.out.println( "No index file found!!!" );
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
        }
        Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadIndex() ends in " + ( System.currentTimeMillis() - begin ) + " ms." );
    }
    
    @Override
	public GraffitoBody removeBody(ID id) {
        GraffitoBody body = null;
        long begin = System.currentTimeMillis();
        Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "]  PersistentCacheMe.removeBody( " + id.toString() + " ) begins" );
        popupBodiesInRange.remove( id );
        try {
            synchronized (bodyIndex) {
                ioCacheSe.delete(cacheDirectory + "B" + id.toString() + ".pgc");
            }
        } catch (Exception ex) {
            Debug.out.println( "removeBody throws " +ex.toString() );
            ex.printStackTrace();
        }
        Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.removeBody( " + id.toString() + " ) ends in " + ( System.currentTimeMillis() - begin ) + " ms." );
        return body;
    }

    @Override
    protected GraffitoHeader removeHeaderInRange(ID id) {
        Debug.out.println( System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.removeHeaderInRange('" + id.toString() + "')" );
        popupBodiesInRange.remove( id );
        return super.removeHeaderInRange( id );
    }
    
    private void saveIndex() {
        Debug.out.println( "saveIndex()");
        try {
            String filename = cacheDirectory + indexFilename;
            File fconn = new File(filename);
            // If no exception is thrown, then the URI is valid, but the file may or may not exist.
            if ( fconn.exists() ) {
                fconn.delete();
                fconn = new File(filename);
            }
            if ( ( headerIndex.getCount() > 0 ) || ( bodyIndex.getCount() > 0 ) ) {
                fconn.createNewFile();
                FileOutputStream os = new FileOutputStream(fconn);
                DataOutputStream dos = new DataOutputStream(os);
                Object o = null;
                
                synchronized (headerIndex) {
                    // write number of header
                    dos.writeInt( headerIndex.getCount() );
                    Header h = null;
                    int size = headerIndex.getSize();
                    for ( int i = 0; i < size; i++ ) {
                        o = headerIndex.get(i);
                        if ( o != null ) {
                            h = (Header)o;
                            h.appendToDataOutput( dos );
                        }
                    }
                }
                
                synchronized (bodyIndex) {
                    dos.writeInt( bodyIndex.getCount() );
                    Body b = null;
                    int size = bodyIndex.getSize();
                    for ( int i = 0; i < size; i++ ) {
                        o = bodyIndex.get(i);
                        if ( o != null ) {
                            b = (Body)o;
                            b.appendToDataOutput( dos );
                        }
                    }
                }
                dos.close();
                os.close();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

  
    @Override
    public void stop() {
        ioCacheSe.queueTask( new Runnable() {

			public void run() {
                try {
                    saveIndex();
                    synchronized( sync ) {
                        sync.notifyAll();
                    }
                } catch (Throwable t) {
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.stop() throws " + t.toString());
                    t.printStackTrace();
                }
            }
        } );

        try {
            synchronized ( sync ) {
                sync.wait();
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        super.stop();
    }
}
